# -*- coding: utf-8 -*-
# @Time: 2021/11/10 14:12
# @Author: lijinxi
# @File    : service_placement_request_schedule.py
# @desc
import copy

import numpy as np
from gurobipy import GRB
import gurobipy as gp
import random
import cvxpy as cp


def next_point(x, y):
    next_points = []
    if 0 < x + 1 < 5 and 0 <= y < 5:
        next_points.append(y * 5 + x + 1)
    if 0 <= x - 1 < 5 and 0 <= y < 5:
        next_points.append(y * 5 + x - 1)
    if 0 <= x < 5 and 0 <= y + 1 < 5:
        next_points.append((y + 1) * 5 + x)
    if 0 <= x < 5 and 0 <= y - 1 < 5:
        next_points.append((y - 1) * 5 + x)
    return next_points


class ServicePlacementRequestSchedule(object):
    def __init__(self, number_access_points=25, number_edge_servers=25, number_requests=200,
                 type_network_functions=10):
        self.number_access_points = number_access_points  # 接入点数量
        self.number_edge_servers = number_edge_servers  # 边缘服务器数量
        self.number_requests = number_requests  # 请求数量
        self.type_network_functions = type_network_functions  # 网络功能种类数

        # 系统设置
        self.computing_capacity_vector = None  # 每种网络功能需要的计算容量
        self.node_availability_vector = None  # 每个边缘服务器的可用性
        self.function_availability_vector = None
        self.delay_between_nodes = None
        self.node_computing_capacity = None  # 节点计算容量
        self.node_communication_capacity = None  # 节点通信容量

        # 用户请求
        self.bandwidth_capacity_vector = None  # 服务放置中不考虑
        self.tolerance_delay_vector = None  # 最大时延
        self.availability_vector = None  # 可用性
        self.ingress_egress_vector = None  # 出入口
        self.process_delay_vector = None  # 节点上的处理时延
        self.network_function_matrix = None  # 指定的网络功能

        # 决策变量
        self.primary_placement = None
        self.selected_access_points = None  # 选择的接入点

        self.initial_points = []
        self.final_delay = None  # 保存迭代算法的最终解

        # 计算临接矩阵
        self.adjacent_access_points = np.zeros(shape=(self.number_access_points, self.number_access_points),
                                               dtype=np.int32)

        self.generate_adjacent_points()
        self.generate_delay_between_nodes()

    def generate_adjacent_points(self):
        for i in range(self.number_access_points):
            y, x = i // 5, i % 5
            self.adjacent_access_points[i][i] = 1.0
            for j in next_point(x, y):
                self.adjacent_access_points[i, j] = 1.0

    def generate_delay_between_nodes(self, fixed_delay=1):
        self.delay_between_nodes = np.zeros((self.number_access_points, self.number_access_points), dtype=np.int64)
        for i in range(0, self.number_access_points):
            for j in range(0, self.number_access_points):
                x_1, y_1 = i % 5, i // 5
                x_2, y_2 = j % 5, j // 5
                self.delay_between_nodes[i][j] = (abs(x_1 - x_2) + abs(y_1 - y_2)) * fixed_delay

    # 初始化用户的请求,后续只更改接入位置,而不更改其他参数
    def initialize_requests(self, computing_capacity_range=None, bandwidth_capacity_range=None,
                            process_delay_range=None, node_availability_range=None, function_availability_range=None,
                            availability_range=None, tolerance_delay_range=None, ratio=2):
        if bandwidth_capacity_range is None:
            bandwidth_capacity_range = [30, 50 + 1]
        if computing_capacity_range is None:
            computing_capacity_range = [50, 100 + 1]
        if process_delay_range is None:
            process_delay_range = [50, 150 + 1]  # us
        if node_availability_range is None:
            node_availability_range = [9990, 9999 + 1]  # [0.99,0.999]
        if function_availability_range is None:
            function_availability_range = [9990, 9999 + 1]  # [0.99,0.999]
        if availability_range is None:
            availability_range = [0.99, 0.999, 0.9999, 0.99999]
        if tolerance_delay_range is None:
            tolerance_delay_range = [20 * 1000, 150 * 1000 + 1]  # [20,150]ms
        np.random.seed(100)  # 保证一致性
        # 设置每个请求的带宽需要
        self.bandwidth_capacity_vector = np.random.randint(*bandwidth_capacity_range, size=(self.number_requests,),
                                                           dtype=np.int8)
        # 设置每种网络功能的计算能力需要
        self.computing_capacity_vector = np.random.randint(*computing_capacity_range,
                                                           size=(self.type_network_functions,),
                                                           dtype=np.int8)
        # 设置物理节点可用性
        self.node_availability_vector = np.random.randint(*node_availability_range, size=(self.number_edge_servers,),
                                                          dtype=np.int64) / 10000.0
        # VNF的可用性
        self.function_availability_vector = np.random.randint(*function_availability_range,
                                                              size=(self.type_network_functions,),
                                                              dtype=np.int64) / 10000.0
        # 单个VNF处理时延
        self.process_delay_vector = np.random.randint(*process_delay_range, size=(self.type_network_functions,),
                                                      dtype=np.int32)
        # TODO:时延容忍, VNF越多时间越长
        self.tolerance_delay_vector = np.random.randint(*tolerance_delay_range, size=(self.number_requests,),
                                                        dtype=np.int64)
        # print(f"tolerance_delay_vector:{self.tolerance_delay_vector}")
        # 生成每个请求的网络功能
        self.network_function_matrix = np.zeros(shape=(self.number_requests, self.type_network_functions),
                                                dtype=np.int8)
        # 设置请求可用性, 链长的可用性较低
        random.seed(100)
        self.availability_vector = np.zeros(shape=(self.number_requests,), dtype=np.float64)
        number_functions = np.random.randint(2, 5 + 1, size=(self.number_requests,), dtype=np.int8)  # 2-5个VNF
        index = [i for i in range(0, self.type_network_functions)]
        for i, number in enumerate(number_functions):
            if number >= 6:
                self.availability_vector[i] = availability_range[0]  # TODO：计算方式
            else:
                self.availability_vector[i] = random.choice(availability_range)  # 随机设置一个
            for j in random.sample(index, number):
                self.network_function_matrix[i][j] = 1
        # print(f"availability vector:{self.availability_vector}")
        # 根据请求的要求, 设置计算容量
        computing_capacity = ratio * np.sum(
            self.network_function_matrix * self.computing_capacity_vector) // self.number_edge_servers + 10
        self.node_computing_capacity = np.zeros(shape=(self.number_edge_servers,), dtype=np.int64)
        for i in range(self.number_edge_servers):
            self.node_computing_capacity[i] = computing_capacity  # 容量都一样
        # 设置通信容量,默认两倍
        communication_capacity = 2 * np.sum(self.bandwidth_capacity_vector) // self.number_edge_servers + 10
        self.node_communication_capacity = np.zeros(shape=(self.number_access_points,), dtype=np.int64)
        for i in range(self.number_access_points):
            self.node_communication_capacity[i] = communication_capacity
        print(f"computing_capacity:{computing_capacity},communication_capacity:{communication_capacity}")

    # 更新每一时刻的轨迹变化
    def update_trace(self, time_slot, debug=False):
        if debug:
            self.initial_points = np.zeros((self.number_requests, self.number_access_points), dtype=np.int8)
            for i in range(self.number_requests):
                self.initial_points[i][i // 8] = 1
        else:
            print(f"load {time_slot}")
            self.initial_points = np.load('../traces/1/time_slot_' + str(time_slot) + '.npy')[:self.number_requests, :]

    # 服务放置阶段，主要考虑主VNF
    def service_placement_stage(self, fixed_rs=None):
        model = gp.Model("service placement request schedule")  # gurobi模型
        model.setParam(GRB.Param.TimeLimit, 5)
        self.primary_placement = model.addMVar(
            shape=(self.number_requests, self.type_network_functions, self.number_edge_servers), vtype=GRB.BINARY,
            name="primary placement")  # 二维转三维, (请求*网络功能数, 边缘服务器数量)
        # v测试时使用
        # self.update_trace(0)  # TODO: 一定要改
        if fixed_rs is not None:
            self.selected_access_points = fixed_rs
        else:
            self.selected_access_points = self.initial_points
        obj = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    obj += (self.selected_access_points[i] @ self.delay_between_nodes @ self.primary_placement[i, j])

        model.setObjective(obj, GRB.MINIMIZE)  # 目标值
        # 被允许的请求，所有VNF被放置
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                # 每个请求的VNF被放在一个节点
                model.addConstr(self.primary_placement[i, j, :].sum() == self.network_function_matrix[i][j],
                                f"place one vnf {i}-{j}")
            # 每个请求的VNF放置不重复
            for k in range(self.number_access_points):
                model.addConstr(self.primary_placement[i, :, k].sum() <= 1, f"not all {i}-{k}")
        # 计算容量限制
        for k in range(self.number_edge_servers):
            _value = 0
            for i in range(self.number_requests):
                _value += self.primary_placement[i, :, k] @ self.computing_capacity_vector
            model.addConstr(_value <= self.node_computing_capacity[k], f"less than {k}")

        model.optimize()
        return self.primary_placement.X

    def request_schedule_stage(self, fixed_sp):
        # 使用cvxpy求解
        self.selected_access_points = cp.Variable(shape=(self.number_requests, self.number_access_points), boolean=True)
        constraints = []
        service_placement = np.array(fixed_sp)
        obj1 = 0
        for i in range(self.number_access_points):
            # 取个平均数
            obj1 += 10 ** 3 * 8 * cp.inv_pos(self.node_communication_capacity[i] - self.selected_access_points[:,
                                                                                   i] @ self.bandwidth_capacity_vector)
        obj2 = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    obj2 += (service_placement[i][j] @ self.delay_between_nodes @ self.selected_access_points[i])
        # self.model.setObjective(obj1 + obj2, GRB.MINIMIZE)

        # 通信容量限制
        for k in range(self.number_access_points):
            # 容量限制
            _value = 0
            for i in range(self.number_requests):
                _value += self.bandwidth_capacity_vector[i] * self.selected_access_points[i, k]
            # self.model.addConstr(_value <= self.node_communication_capacity[k], f"less than {k}")
            constraints += [_value <= self.node_computing_capacity[k]]

        # 选择接入点
        for i in range(self.number_requests):
            # TODO:修改邻接点的写法
            for j in range(self.number_access_points):
                if self.initial_points[i][j] == 1:
                    constraints += [self.selected_access_points[i] @ self.adjacent_access_points[j] == 1]
        # self.model.optimize()
        prob = cp.Problem(cp.Minimize(obj1 + obj2), constraints)
        prob.solve(solver=cp.GUROBI, TimeLimit=10)
        print(prob.value, prob.status)
        real_obj1 = 0
        for i in range(self.number_access_points):
            real_obj1 += 10 ** 3 * cp.sum(self.selected_access_points[:, i]) * \
                         cp.inv_pos(self.node_communication_capacity[i] - self.selected_access_points[:, i] @
                                    self.bandwidth_capacity_vector)
        self.final_delay = real_obj1.value + obj2.value
        print(real_obj1.value, obj2.value, self.final_delay)
        return self.selected_access_points.value

    def iteration_algorithm(self, tolerance_delay=100, max_iter=10):
        current_delay, last_delay = 0, np.inf
        fixed_request_schedule = None
        for i in range(max_iter):
            fixed_service_placement = self.service_placement_stage(fixed_request_schedule)
            fixed_request_schedule = self.request_schedule_stage(fixed_service_placement)
            current_delay = self.final_delay
            print("***" * 20, current_delay)
            if last_delay < current_delay + tolerance_delay:
                return fixed_service_placement, fixed_request_schedule, current_delay, i
            else:
                last_delay = current_delay

    # 切换时延最小化的求解
    def switch_minimum(self, last_service_placement=None):
        self.selected_access_points = np.zeros(shape=(self.number_requests, self.number_access_points))
        print(">" * 20, np.sum(self.selected_access_points))
        if last_service_placement is None:
            last_service_placement = np.zeros(
                shape=(self.number_requests, self.type_network_functions, self.number_access_points), dtype=np.int8)
        # 随机选择接入点,TODO：这儿有问题
        node_communication_capacity = copy.deepcopy(self.node_communication_capacity)  # 天哪
        for i in range(self.number_requests):
            for j in range(self.number_access_points):
                if self.initial_points[i][j] == 1:
                    for k in range(self.number_access_points):
                        if self.adjacent_access_points[j][k] == 1:
                            if node_communication_capacity[k] > self.bandwidth_capacity_vector[i] and \
                                    node_communication_capacity[k] >= 0.2 * self.node_communication_capacity[k]:
                                node_communication_capacity[k] -= self.bandwidth_capacity_vector[i]
                                self.selected_access_points[i][k] = 1
                                break
        for i in range(self.number_requests):  # 重新处理一下
            for j in range(self.number_access_points):
                if self.initial_points[i][j] == 1 and self.selected_access_points[i].sum() != 1:  # 没有被放置
                    for k in range(self.number_access_points):
                        if node_communication_capacity[k] > self.bandwidth_capacity_vector[i] and \
                                node_communication_capacity[k] >= 0.2*self.node_communication_capacity[k]:
                            node_communication_capacity[k] -= self.bandwidth_capacity_vector[i]
                            self.selected_access_points[i][k] = 1
                            break
        print(">" * 20, np.sum(self.selected_access_points), node_communication_capacity.any() < 0)

        model = gp.Model("switch minimum")  # gurobi模型
        model.setParam(GRB.Param.TimeLimit, 1.2)
        self.primary_placement = model.addMVar(
            shape=(self.number_requests, self.type_network_functions, self.number_edge_servers), vtype=GRB.BINARY,
            name="primary placement")  # 二维转三维, (请求*网络功能数, 边缘服务器数量)

        obj = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    obj += (self.selected_access_points[i] @ self.delay_between_nodes @ self.primary_placement[i, j])

        obj2 = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    obj2 += (1 - last_service_placement[i, j] @ self.primary_placement[i, j]) * \
                            self.computing_capacity_vector[j]

        obj2 *= 0.05
        model.setObjective(obj + obj2, GRB.MINIMIZE)  # 目标值
        # 所有VNF被放置
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                # 每个请求的VNF被放在一个节点
                model.addConstr(self.primary_placement[i, j, :].sum() == self.network_function_matrix[i][j],
                                f"place one vnf {i}-{j}")
            # 每个请求的VNF放置不重复
            for k in range(self.number_access_points):
                model.addConstr(self.primary_placement[i, :, k].sum() <= 1, f"not all {i}-{k}")
        # 计算容量限制
        for k in range(self.number_edge_servers):
            _value = 0
            for i in range(self.number_requests):
                _value += self.primary_placement[i, :, k] @ self.computing_capacity_vector
            model.addConstr(_value <= self.node_computing_capacity[k], f"less than {k}")

        model.optimize()

        # 添加上排队时延
        obj3 = 0
        for i in range(self.number_access_points):
            obj3 += 10 ** 3 * cp.sum(self.selected_access_points[:, i]) * cp.inv_pos(
                self.node_communication_capacity[i] - self.selected_access_points[:,
                                                      i] @ self.bandwidth_capacity_vector)

        real_obj = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    real_obj += (self.selected_access_points[i] @ self.delay_between_nodes @ self.primary_placement.X[
                        i, j])

        real_obj2 = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    real_obj2 += (1 - last_service_placement[i, j] @ self.primary_placement.X[i, j]) * \
                                 self.computing_capacity_vector[j]
        real_obj2 *= 0.05
        res = model.objVal + obj3.value
        print(real_obj, real_obj2, obj3.value)
        return self.primary_placement.X, res

    def queue_minimum(self, last_service_placement=None):
        if last_service_placement is None:
            last_service_placement = np.zeros(
                shape=(self.number_requests, self.type_network_functions, self.number_access_points), dtype=np.int8)
        # 使用cvxpy求解
        # 服务放置尽可能的靠近用户
        model = gp.Model("service placement close")  # gurobi模型
        model.setParam(GRB.Param.TimeLimit, 10)
        self.primary_placement = model.addMVar(
            shape=(self.number_requests, self.type_network_functions, self.number_edge_servers), vtype=GRB.BINARY,
            name="primary placement")  # 二维转三维, (请求*网络功能数, 边缘服务器数量)
        # v测试时使用
        # self.update_trace(0)  # TODO: 一定要改
        self.selected_access_points = self.initial_points  # 最开始的初始化，靠近用户
        obj = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    obj += (self.selected_access_points[i] @ self.delay_between_nodes @ self.primary_placement[i, j])

        model.setObjective(obj, GRB.MINIMIZE)  # 目标值
        # 被允许的请求，所有VNF被放置
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                # 每个请求的VNF被放在一个节点
                model.addConstr(self.primary_placement[i, j, :].sum() == self.network_function_matrix[i][j],
                                f"place one vnf {i}-{j}")
            # 每个请求的VNF放置不重复
            for k in range(self.number_access_points):
                model.addConstr(self.primary_placement[i, :, k].sum() <= 1, f"not all {i}-{k}")
        # 计算容量限制
        for k in range(self.number_edge_servers):
            _value = 0
            for i in range(self.number_requests):
                _value += self.primary_placement[i, :, k] @ self.computing_capacity_vector
            model.addConstr(_value <= self.node_computing_capacity[k], f"less than {k}")

        model.optimize()
        print(model.objVal)

        # 优化网络选择
        self.selected_access_points = cp.Variable(shape=(self.number_requests, self.number_access_points), boolean=True)
        constraints = []
        obj1 = 0
        for i in range(self.number_access_points):
            # 取个平均数
            obj1 += 10 ** 3 * 8 * cp.inv_pos(self.node_communication_capacity[i] - self.selected_access_points[:,
                                                                                   i] @ self.bandwidth_capacity_vector)
        # 通信容量限制
        for k in range(self.number_access_points):
            # 容量限制
            _value = 0
            for i in range(self.number_requests):
                _value += self.bandwidth_capacity_vector[i] * self.selected_access_points[i, k]
            # self.model.addConstr(_value <= self.node_communication_capacity[k], f"less than {k}")
            constraints += [_value <= self.node_computing_capacity[k]]

        # 选择接入点
        for i in range(self.number_requests):
            # TODO:修改邻接点的写法
            for j in range(self.number_access_points):
                if self.initial_points[i][j] == 1:
                    constraints += [self.selected_access_points[i] @ self.adjacent_access_points[j] == 1]
        # self.model.optimize()
        prob = cp.Problem(cp.Minimize(obj1), constraints)
        prob.solve(solver=cp.GUROBI, TimeLimit=10)
        print(prob.value, prob.status)
        real_obj1 = 0
        for i in range(self.number_access_points):
            real_obj1 += 10 ** 3 * cp.sum(self.selected_access_points[:, i]) * \
                         cp.inv_pos(self.node_communication_capacity[i] - self.selected_access_points[:, i] @
                                    self.bandwidth_capacity_vector)

        obj2 = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    obj2 += (1 - last_service_placement[i, j] @ self.primary_placement.X[i, j]) * \
                            self.computing_capacity_vector[j]

        obj2 *= 0.05
        # 重新计算通信时延
        real_obj = 0
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                if self.network_function_matrix[i][j] == 1:
                    real_obj += (self.selected_access_points[i] @ self.delay_between_nodes @ self.primary_placement.X[
                        i, j])

        res = real_obj.value + real_obj1.value + obj2
        print(real_obj.value, real_obj1.value, obj2, res)
        return self.primary_placement.X, res


if __name__ == '__main__':
    sprs = ServicePlacementRequestSchedule()
    sprs.initialize_requests()
    sprs.update_trace(time_slot=1)
    # sprs.iteration_algorithm()
    sprs.switch_minimum()
    # sprs.queue_minimum()
